﻿using System;
using WiMo.Games;
using WiMo.Games.Inputs;
using System.Collections.Generic;
using System.Threading;
using WiMo.Games.Drawables;
using System.Windows.Forms;
using WiMo.Games.Menus;
using System.Runtime.InteropServices;
using Microsoft.WindowsMobile.Status;
//using OpenNETCF.WindowsCE.Notification;

namespace WiMo.Games.WM5
{
    public class GameEngine : IGameEngine
    {
        [DllImport("CoreDll")]
        private extern static bool NLedGetDeviceInfo(Int32 nID, NLED_COUNT_INFO nci);

        [DllImport("CoreDll")]
        private extern static bool NLedGetDeviceInfo(Int32 nID, NLED_SUPPORTS_INFO nsi);

        [DllImport("CoreDll")]
        private extern static bool NLedGetDeviceInfo(Int32 nID, NLED_SETTINGS_INFO nsi);

        [DllImport("CoreDll")]
        private extern static bool NLedSetDevice(Int32 nID, NLED_SETTINGS_INFO nsi);

        public const Int32 NLED_COUNT_INFO_ID = 0;
        public const Int32 NLED_SUPPORTS_INFO_ID = 1;
        public const Int32 NLED_SETTINGS_INFO_ID = 2;

        private float _fps;
        private bool _hopperRunning = false;
        int _frameCount;

        //private Led _leds;
        IntPtr _hwnd;

        public Form Form { get; private set; }

        internal static GameEngine GameEngineInstance;
        public Random Randomizer { get; private set; }
        public List<IGameComponent> Components { get; set; }
        private List<IDrawable> _images;
        private SystemState _incomingCall;
        private bool _initialized = false;

        private bool _running = true;
        private bool _gameInBackground = false;
        private Size _originalSize;
        private ISpriteText _diagnosticsText = null;
        /// <summary>
        /// Initializes a new instance of the GameEngine class.
        /// </summary>
        public GameEngine()
        {
            Form = new GameForm();
            Form.WindowState = FormWindowState.Maximized;
            Form.Menu = null;
            Form.Text = string.Empty;
            Form.ControlBox = false;
            Form.AutoScaleMode = AutoScaleMode.Dpi;

            _incomingCall = new SystemState(SystemProperty.PhoneIncomingCall);
            _incomingCall.Changed += _incomingCall_Changed;

            (Form as GameForm).GameFormResize += GameEngine_GameFormResize;
            (Form as GameForm).Continue += GameEngine_Continue; 
            _hwnd = Form.Handle;

            _originalSize = new Size(Form.Width, Form.Height);

            Randomizer = new Random(DateTime.Now.Second * DateTime.Now.Minute);

            AssetFactory = new AssetFactory(this);
            Components = new List<IGameComponent>();
            ContentManager = new ContentManager(this);

            _images = new List<IDrawable>();

            DefaultFont = new SpriteFont { Name = "Aria", Size = 10.0f, Bold = true };
            DiagnosticsLocation = new PointF(10, 10);
            EnableDiagnostics = false;

            GameEngine.GameEngineInstance = this;

            IsPaused = false;
        }

        void GameEngine_Continue(object sender, EventArgs e)
        {
            _gameInBackground = false;
            (Form as GameForm).FormEnabled = false;
        }

        void _incomingCall_Changed(object sender, ChangeEventArgs args)
        {
            IsPaused = true;
            _gameInBackground = true;

            System.Diagnostics.Debug.WriteLine("INCOMING PHONE CALL");

            Form.Invoke((Action)delegate
            {
                if(args.NewValue != null)
                    System.Diagnostics.Debug.WriteLine(args.NewValue);

                if (_initialized)
                {
                    var theForm = (Form as GameForm);
                    if (theForm != null)
                    {
                        theForm.SetStatus("Tap Screen to Continue");
                        theForm.FormEnabled = true;
                    }
                    
                    Form.Invalidate();
                    if (Convert.ToInt32(args.NewValue) == 1)
                        Form.Hide();
                    else
                        Form.Show();
                }
            });
        }

        void GameEngine_GameFormResize(object sender, EventArgs e)
        {
            if (_initialized)
            {                
                var theForm = (Form as GameForm);
                
                if (theForm != null)
                {
                    if (theForm.Size.Width != _originalSize.Width && theForm.Size.Height != _originalSize.Height)
                    {
                        _gameInBackground = true;
                        theForm.SetStatus("Rotation Not Supported");
                        theForm.FormEnabled = true;
                    }
                    else
                    {
                        _gameInBackground = false;
                        theForm.FormEnabled = false;
                    }

                }

                Display.Resize(new Size(Form.Size.Width, Form.Size.Height));

                Form.Invalidate();
            }
        }

        public void Initialize(IGame game)
        {
            Game = game;
            game.Engine = this;
        }

        public void OnResize(Size bounds)
        {
            //Handled w/ event from from.
        }

        public void AddGameComponent(IGameComponent component)
        {
            Components.Add(component);
        }

        private int _wait;
        private int _totalFrame;

        public void Start()
        {
            GameTime = new GameTime();
            var fpsTimer = System.Diagnostics.Stopwatch.StartNew();
            _fps = 0;
            _frameCount = 0;
            int renderMs = 0;
            int collisionMs = 0;
            int addMs = 0;
            Form.Show();

            fpsTimer.Start();

            _initialized = true;

            var gameThread = new Thread(() =>
                {
                    string debugMessage = string.Empty;

                    Game.CreateMenus();

                    Game.LoadContent();
                    foreach (var component in Components)
                        component.LoadContent(ContentManager);

                    Game.Initialize();
                    foreach (var component in Components)
                        component.Initialize();

                    Game.BeginRun();

                    var timer = System.Diagnostics.Stopwatch.StartNew();
                    var gameTimer = System.Diagnostics.Stopwatch.StartNew();
                    
                    bool updateIt = false;
                    while (_running)
                    {
                        if (!_gameInBackground)
                        {
                            if (EnableDiagnostics)
                                _frameCount++;

                            Display.Reset();

                            if (!IsPaused)
                                GameTime.Update(gameTimer.ElapsedMilliseconds);

                            gameTimer.Reset();
                            gameTimer.Start();

                            //Speed things up a tad, only update
                            //the input manager after every other move.
                            if (updateIt)
                            {
                                InputManager.Update();

                                Game.Update(GameTime);
                                foreach (var component in Components)
                                    component.Update(GameTime);

                                //Sorta a hack, we don't want to 
                                //process the same touch events more 
                                //than once.
                                InputManager.TouchPoints.Clear();
                            }
                            else
                            {
                                Game.Update(GameTime);
                                foreach (var component in Components)
                                    component.Update(GameTime);
                            }

                            updateIt = !updateIt;

                            int physicsMS = 0;
                            if (!IsPaused)
                            {

                                timer.Reset();
                                timer.Start();
                                PhysicsManager.Update(GameTime);
                                physicsMS = (int)timer.ElapsedMilliseconds;
                                timer.Reset();
                                timer.Start();
                                CollisionManager.Update(GameTime);
                                collisionMs = (int)timer.ElapsedMilliseconds;
                            }
                            else
                                collisionMs = 0;


                            timer.Reset();
                            timer.Start(); 
                            Game.Draw(GameTime);

                            foreach (var component in Components)
                                component.Draw(GameTime);

                            InputManager.Draw(GameTime);

                            addMs = (int)timer.ElapsedMilliseconds;

                            timer.Reset();
                            timer.Start();

                            if(EnableDiagnostics && _diagnosticsText != null)
                                Display.Add(_diagnosticsText);

                            if (_running)
                                Display.Render();

                            renderMs = (int)timer.ElapsedMilliseconds;

                            //Target Framerate is 30FPS or 33 MS per frame.
                            _wait = (int)(33 - gameTimer.ElapsedMilliseconds);
                            _totalFrame += (int)gameTimer.ElapsedMilliseconds;

                            if (EnableDiagnostics)
                            {
                                if (fpsTimer.ElapsedMilliseconds > 1000)
                                {                                    
                                    debugMessage = string.Format("A:{0:000} FPS:{1:00} D:{2:000} R:{3:000} C:{4:000} P:{5:000}", _totalFrame / _fps, _fps, addMs, renderMs, collisionMs, physicsMS);

                                    if (_diagnosticsText == null)
                                        _diagnosticsText = AssetFactory.CreateText();

                                    _diagnosticsText.Position = DiagnosticsLocation;
                                    _diagnosticsText.ForeColor = DiagnosticsFontColor;
                                    _diagnosticsText.Font = DefaultFont;
                                    _diagnosticsText.Text = debugMessage;
                                    _diagnosticsText.Origin = Origin.TopLeft;
                                    _diagnosticsText.Size = new Size(230, 20);

                                    _fps = _frameCount;
                                    _frameCount = 0;
                                    _totalFrame = 0;
                                    fpsTimer.Reset();
                                    fpsTimer.Start();
                                   // System.Diagnostics.Debug.WriteLine(debugMessage);
                                }
                            }

                            _wait = Math.Max(1, _wait);

                            //System.Diagnostics.Debug.WriteLine("WAIT BTW THRED->" + _wait);

                            System.Threading.Thread.Sleep(_wait);
                        }
                        else
                            System.Threading.Thread.Sleep(1000);
                    }

                    EndVibrate();

                    Form.Invoke(new ThreadStart(() => { Form.Close(); }));
                    ShutDown();
                    return;
                });

            gameThread.Priority = ThreadPriority.Highest;
            gameThread.Start();
        }

        private void ShutDown()
        {
            Display.Dispose();
            ContentManager.Dispose();
        }

        public GameTime GameTime
        {
            get;
            set;
        }

        public void Exit()
        {
            if (!_hopperRunning)
            {                
                _running = false;
            }
        }        

        public void StartHopper()
        {
            InputManager.StartHopper();
            _hopperRunning = true; 
        }

        public void StopHopper()
        {
            InputManager.StopHopper();
            _hopperRunning = false;
        }

        public IGame Game { get; private set; }

        public void RegisterComponent(IGameComponent component)
        {
            component.InputManager = InputManager;
            component.Display = Display;
            component.Engine = this;
            Components.Add(component);
        }

        public void Pause()
        {
            IsPaused = true;
        }

        public void Resume()
        {
            IsPaused = false;
        }

        private bool _enableDiagnostics;
        public bool EnableDiagnostics
        {
            get
            {
                return _enableDiagnostics;
            }
            set
            {
                if (Display != null)
                {
                    Display.Diagnostics = value;
                    Display.Invalidate();
                }

                _enableDiagnostics = value;
            }
        }
        public bool IsPaused { get; set; }
        public Color DiagnosticsFontColor { get; set; }
        public PointF DiagnosticsLocation { get; set; }

        public IAssetFactory AssetFactory { get; set; }
        public IContentManager ContentManager { get; set; }
        public ICollisionManager CollisionManager { get; set; }
        public IPhysicsManager PhysicsManager { get; set; }
        public IInputManager InputManager { get; set; }
        public IAudioManager AudioManager { get; set; }
        public IMenuManager MenuManager { get; set; }
        public IDisplay Display { get; set; }
        public ISpriteFont DefaultFont { get; set; }
        public IStateManager StateManager { get; set; }

        public void Vibrate(int ms)
        {
            new Thread(() =>
            {
                BeginVibrate();
                Thread.Sleep(ms);
                EndVibrate();
            }).Start();
        }

        public void BeginVibrate()
        {
            SetLedStatus(1, LedFlags.STATE_ON);
        }

        public void EndVibrate()
        {
            SetLedStatus(1, LedFlags.STATE_OFF);
        }

        private void SetLedStatus(uint idx, LedFlags state)
        {
            var nsi = new NLED_SETTINGS_INFO(); ;
            nsi.LedNum = idx;
            nsi.OnOffBlink = state;

            NLedSetDevice(NLED_SETTINGS_INFO_ID, nsi);
        }

        public object GraphicsManager
        {
            get { throw new NotImplementedException(); }
        }
    }

    public class NLED_COUNT_INFO
    {
        public UInt32 cLeds;
    }

    public class NLED_SUPPORTS_INFO
    {
        public UInt32 Lednum;
        public Int32 lCycleAdjust;
        public bool fAdjustTotalCycleTime;
        public bool fAdjustOnTime;
        public bool fAdjustOffTime;
        public bool fMetaCycleOn;

        public bool fMetaCycleOff;
    }

    [Flags]
    public enum LedFlags : int
    {
        STATE_OFF = 0x0000,  /* dark LED */
        STATE_ON = 0x0001,  /* light LED */
        STATE_BLINK = 0x0002,  /* flashing LED */
    }

    public class NLED_SETTINGS_INFO
    {
        public UInt32 LedNum;
        public LedFlags OnOffBlink;
        public Int32 TotalCycleTime;
        public Int32 OnTime;
        public Int32 OffTime;
        public Int32 MetaCycleOn;
        public Int32 MetaCycleoff;
    }
}
